home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1999 April: Mac OS SDK / Dev.CD Apr 99 SDK1.toast / Development Kits / Zoomed Video Driver v1.0 SDK / Tools / PC Card DispNameReg / Src / TwistDownList.c < prev    next >
Encoding:
C/C++ Source or Header  |  1997-06-02  |  66.3 KB  |  2,197 lines  |  [TEXT/CWIE]

  1. /*                                    TwistDownList.c                                */
  2. /*
  3.  * List In A List Sample
  4.  * TwistDownList.c
  5.  * Copyright © 1993-94 Apple Computer Inc. All rights reserved.
  6.  *
  7.  * TwistDownList manages all aspects of the "twist-down" list, a one-column text
  8.  * list where each row contains a triangular button. The button may be in one of
  9.  * two states: closed and opened. When opened, sub-elements to this row are
  10.  * displayed. The twist-down list is generally based on code from NewsWatcher
  11.  * by Steve Falkenburg, but Steve says that the code was originally written by
  12.  * John Norstad.
  13.  *
  14.  * Applications must provide a LDEF stub resource by adding the following (or
  15.  * something equivalent) to the resource definition file:
  16.  *        #ifndef LDEF_Stub
  17.  *        #define LDEF_Stub    1024
  18.  *        #endif
  19.  *        type 'LDEF' {
  20.  *        unsigned hex longint = $41FA0006;    //    lea        pc+8,a0    ; a0 -> ProcPtr
  21.  *        unsigned hex int = $2050;            //    movea.l    (a0),a0    ; a0 -> LDEF
  22.  *        unsigned hex int = $4ED0;            //    jmp        (a0)    ; jump to it
  23.  *        unsigned hex longint = 0;            //    dc.l    0        ; LDEF ProcPtr
  24.  *        };
  25.  *        resource 'LDEF' (LDEF_Stub, "Stub LDEF", preload, locked) { };
  26.  *
  27.  * Because of the way that the callback LDEF is managed, TwistDownList.c must be
  28.  * in the application's root segment. If this is inappropriate, the segement
  29.  * resource must be marked "preload" and "locked" -- otherwise, your program will
  30.  * crash. (Note that this applies only to 68000 code.)
  31.  */
  32. #include <stdio.h>
  33. #include "TwistDownList.h"
  34. #include "Palettes.h"
  35. //#include "IntlResources.h"
  36. //#include "Script.h"
  37. #include "TextUtils.h"
  38. #include <Kernel.h>
  39. //#include "SegLoad.h"
  40. //#include "FixMath.h"
  41. //#include "ToolUtils.h"
  42. //#include "Gestalt.h"
  43. //#include "Resources.h"
  44. //#include "Packages.h"
  45. #ifdef __powerc
  46. /*
  47.  * PowerPC System area locations in "Get" and "Set" functions. They are
  48.  * defined in LowMem.h
  49.  */
  50. #include <LowMem.h>
  51. #else
  52. #ifdef MPW
  53. /*
  54.  * MPW requires a reference to a low memory global to set hiliting
  55.  * Think C and MetroWorks includes SysEqu.h in the default MacHeaders file.
  56.  */
  57. #include <SysEqu.h>
  58. #endif
  59. #endif
  60.  
  61. #define UNUSED(what) do {    \
  62.             what;            \
  63.         } while (0)
  64.  
  65. #ifndef MONOCHROME_FILL
  66. #define MONOCHROME_FILL    0
  67. #endif
  68.  
  69. #define LIST    (**theList)
  70. /*
  71.  * A common List Selection flag variation that equates Shift and Command keys.
  72.  */
  73. #define SELECTION_FLAGS    \
  74.     (lUseSense | lNoRect | lNoExtend | lNoNilHilite | lDoVAutoscroll)
  75. /*
  76.  * SELECTION_FLAGS, by default, allows a single list cell to be selected.
  77.  */ 
  78. #ifndef SELECTION_FLAGS
  79. #define SELECTION_FLAGS (lOnlyOne | lNoNilHilite | lDoVAutoscroll)
  80. #endif
  81.  
  82. enum {
  83.     kScrollBarWidth            = 16,
  84.     kScrollBarOffset        = kScrollBarWidth - 1,
  85.     kActiveControl            = 0,                    /* Normal button, no hilite    */
  86.     kDisabledControl        = 255,                    /* Disabled button hilite    */
  87.     kZeroIndent                = 4                        /* Indent if scrollbar == 0    */
  88. };
  89. /*
  90.  * This is a nominal value for the maximum cell width. It might be better as a
  91.  * user-settable parameter.
  92.  */
  93. #define kMaxHorizontalScroll    (CharWidth('M') * 255)
  94.  
  95. /*
  96.  * kAnimationDelay is the number of ticks to display the intermediate
  97.  * "twisting" glyph.
  98.  */
  99. #define kAnimationDelay        3L
  100. /*
  101.  * The triangle gap parameters define the amount of space to display to the
  102.  * left and right of the twist-down triangle. The "outside gap" is to the left
  103.  * on Roman-alphabet scripts and on the right on Hebrew or Arabic scripts.
  104.  * The default values are suitable for small font sizes, but could be increased
  105.  * for large sizes.
  106.  */
  107. #define kTriangleOutsideGap        (1)                    /* From margin to button    */
  108. #define kTriangleInsideGap        (2)                    /* From button to text        */
  109. #define kScrollBarWidth            (16)                /* Width of a scroll bar    */
  110.  
  111. /*
  112.  * Parameters for the print handler.
  113.  */
  114. #define kPrintoutHeaderFont        "\pHelvetica"
  115. #define kPrintoutHeaderFontSize    9
  116. #define kPrintoutHeaderGap        4
  117. #define kPrintoutHeaderStyle    bold
  118.  
  119. /*
  120.  * The List's userHandle contains our private context information. The PolyHandles
  121.  * are used to draw the "triangle" buttons. Note that they are drawn to the list
  122.  * cell height. When it changes, the triangles will be reconstructed. The fontSize,
  123.  * fontNumber, and isLeftJustify variables are used to draw the list cell content.
  124.  */
  125. struct TwistDownPrivateRecord {
  126.         TwistDownDrawProc    drawProc;                /* This draws the cell data    */
  127.         PolyHandle            openTriangle;            /* The "expanded" button    */
  128.         PolyHandle            closedTriangle;            /* The "closed" button        */
  129.         PolyHandle            intermediateTriangle;    /* The "expanding" button    */
  130.         ControlHandle        hScroll;                /* List's horiz. scrollbar    */
  131.         short                tabIndent;                /* NewTwistDownList param    */
  132.         short                fontSize;                /* for TextSize                */
  133.         short                fontNumber;                /* for TextFont                */
  134.         Boolean                canHiliteSelection;        /* TRUE if hilite ok        */
  135.         Boolean                isLeftJustify;            /* GetSysJust value            */
  136.         short                triangleWidth;            /* Twist-down button width    */
  137. };
  138. typedef struct TwistDownPrivateRecord    TwistDownPrivateRecord,
  139.         *TwistDownPrivatePtr, **TwistDownPrivateHdl;
  140.  
  141. /*
  142.  * The de-referenced private data record is too long to type each time it appears
  143.  * so it will be defined by the PRIVATE macro.
  144.  */
  145. #define PRIVATE            (**((TwistDownPrivateHdl) (LIST.userHandle)))
  146. /*
  147.  * These macros simplify access to the flag word in the list element.
  148.  */
  149. #define SetTDFlag(elementHdl, mask)        ((**elementHdl).flag |= (mask))
  150. #define ClearTDFlag(elementHdl, mask)    ((**elementHdl).flag &= ~(mask))
  151. #define InvertTDFlag(elementHdl, mask)    ((**elementHdl).flag ^= (mask))
  152. #define TestTDFlag(elementHdl, mask)    (((**elementHdl).flag & (mask)) != 0)
  153.  
  154. #define SIBLING            (*twistDownSiblingSetPtr)
  155.  
  156. /*
  157.  * The proper way to build a compiled-in LDEF is to plug a transfer address into a
  158.  * stub code resource. The StubRecord must track any changes in the LDEF resource.
  159.  */
  160. #ifdef __powerc
  161. #pragma options align=mac68k
  162. #endif
  163. struct StubRecord {
  164.     long            lea;            /*    Lea            (pc)+8,a0                    */
  165.     short            movea;            /*    Movea.l        (a0),a0                        */
  166.     short            jmp;            /*    jmp            (a0)                        */
  167.     ListDefUPP        ldefAddress;    /*    dc.l        0                            */
  168. };
  169. typedef struct StubRecord    **StubHandle;
  170. #ifdef __powerc
  171. #pragma options align=reset
  172. #endif
  173.  
  174. /*
  175.  * Local (private) functions.
  176.  */
  177. /*
  178.  * Dispose of a PolyHandle. This must be a macro. The argument
  179.  * may not have side-effects.
  180.  */
  181. #define ForgetPoly(thePoly) do {        \
  182.         if (thePoly != NULL) {             \
  183.             KillPoly(thePoly);            \
  184.             thePoly = NULL;                \
  185.         }                                \
  186.     } while (0)
  187. #define height(r)        ((r).bottom - (r).top)
  188. #define width(r)        ((r).right - (r).left)
  189.  
  190. static short                CountVisibleElements(
  191.         TwistDownHdl            twistDownHandle
  192.     );
  193. static void                 ClearSelectedElementBit(
  194.         TwistDownHdl            twistDownHandle
  195.     );
  196. static void                    CopySelectionStateToList(
  197.         ListHandle                theList,
  198.         short                    selectedRow
  199.     );
  200. static void                    SetElementsInList(
  201.         ListHandle                theList,
  202.         TwistDownHdl            twistDownHandle,
  203.         Cell                    *currentCell
  204.     );
  205. static pascal void            TwistDownLDEF(
  206.         short                    listMessage,
  207.         Boolean                    listSelect,
  208.         Rect                    *listRect,
  209.         Cell                    listCell,
  210.         short                    listDataOffset,
  211.         short                    listDataLen,
  212.         ListHandle                listHandle
  213.     );
  214. static void                    DrawTriangle(
  215.         PolyHandle                polyHandle,
  216.         Point                    polyPoint,
  217.         Boolean                    isSelected
  218.     );
  219. static pascal void            DefaultTwistDownDrawProc(
  220.         ListHandle                theList,            /* The list itself            */
  221.         TwistDownPtr            twistDownPtr,        /* Locked data handle        */
  222.         const Rect                *viewRect            /* Draw in this area        */
  223.     );
  224.  
  225. /*
  226.  * Horizontal scrollbar stuff
  227.  */
  228. static void                    AdjustHorizontalScrollbar(
  229.         ListHandle                theList
  230.     );
  231. static pascal void            ScrollTwistDownActionProc(
  232.         register ControlHandle    theControl,
  233.         short                    partCode
  234.     );
  235. static void                    ScrollTwistDownList(
  236.         register ControlHandle    theControl
  237.     );
  238.  
  239. static void                    pstrcpy(
  240.         StringPtr                dst,
  241.         ConstStr255Param        src
  242.     );
  243. static void                    pstrcat(
  244.         StringPtr                dst,
  245.         ConstStr255Param        src
  246.     );
  247.  
  248. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  249.  * NewTwistDownList
  250.  *
  251.  * Create a twist-down list. Before calling, you must set the port to the current
  252.  * window and specify the font and font size that is to be used to draw the list.
  253.  * NewTwistDownList creates an empty one-column list with vertical and horizontal
  254.  * scroll bars. Only one item may be selected at a time; but this could be changed
  255.  * by the application without difficulty. The scrollbars are managed internally.
  256.  *
  257.  * Note: unlike the previous release of TwistDownList, the viewRect does not
  258.  * include the scroll bars. For a fullscreen list, do the following:
  259.  *        viewRect = FrontWindow()->portRect;
  260.  *        viewRect.right -= kScrollBarWidth;
  261.  *        viewRect.bottom -= kScrollBarWidth;
  262.  *
  263.  * The tabIndent parameter should be set to the amount to indent successive levels
  264.  * (zero means no indentation). Setting it to the widMax value from the current
  265.  * font seems reasonable.
  266.  *
  267.  * canHiliteSelection should be TRUE for normal selection (the selection is
  268.  * hilited). It should be FALSE if you want to supress selection. Because
  269.  * hirearchical lists often "select" by revealing a sub-topic, this may be
  270.  * more reasonable in many cases.
  271.  *
  272.  * isLeftJustify should be set TRUE for systems using the Roman alphabet. It would
  273.  * be set FALSE for right-to-left languages such as Arabic and Hebrew. It is used
  274.  * to configure the direction of the buttons and the location of text within
  275.  * the displayed list cell.
  276.  *
  277.  * We manage the horizontal scrollbar by some private trickery -- this lets the
  278.  * list pane be smaller than the amount of text to be displayed. 
  279.  */
  280. pascal ListHandle
  281. NewTwistDownList(
  282.         const Rect                *viewRect,
  283.         TwistDownDrawProc        drawProc,
  284.         unsigned short            tabIndent,
  285.         Boolean                    canHiliteSelection,
  286.         Boolean                    isLeftJustify,
  287.         Boolean                    hasGrowBox
  288.     )
  289. {
  290.         register TwistDownPrivatePtr    privatePtr;
  291.         ListHandle                theList;
  292.         short                    listHeight;
  293.         Point                    cellSize;
  294.         Rect                    dataBounds;
  295.         Rect                    listRect;
  296.         FontInfo                info;
  297.         short                    listFontHeight;
  298.         GrafPtr                    currentPort;
  299.         ListDefUPP                listDefProc;
  300.         StubHandle                stubHandle;
  301.  
  302.         theList = NULL;
  303.         GetPort(¤tPort);
  304.         GetFontInfo(&info);
  305.         listFontHeight = info.ascent + info.descent + info.leading;
  306.         /*
  307.          * Define the list drawing area. If the list viewRect.bottom
  308.          * is less than the portRect.bottom, adjust the list area height
  309.          * integral number of rows will be drawn. If equal, the list
  310.          * area abuts the bottom of the display window and we shouldn't
  311.          * change the bottom or the scroll bars will look wierd.
  312.          */
  313.         listRect = *viewRect;
  314.         SetPt(&cellSize, width(listRect), listFontHeight);
  315.         if ((listRect.bottom + kScrollBarWidth) < currentPort->portRect.bottom) {
  316.             listHeight = height(listRect);
  317.             listHeight -= (listHeight % listFontHeight);
  318.             listRect.bottom = listRect.top + listHeight;
  319.         }
  320.         /*
  321.          * Define a one-column list.
  322.          */
  323.         listDefProc = NewListDefProc(TwistDownLDEF);
  324.         stubHandle = (StubHandle) GetResource('LDEF', LDEF_Stub);
  325.         if (stubHandle == NULL)
  326.             goto exit;                    /* Failure                                */
  327.         (**stubHandle).ldefAddress = listDefProc;
  328.         HNoPurge((Handle) stubHandle);
  329.         SetRect(&dataBounds, 0, 0, 1, 0);
  330.         theList = LNew(
  331.                 &listRect,                /* Viewing area                            */
  332.                 &dataBounds,            /* Rows and col's                        */
  333.                 cellSize,                /* Element size                            */
  334.                 LDEF_Stub,                /* Callback defproc                        */
  335.                 currentPort,            /* Display window                        */
  336.                 TRUE,                    /* Draw it                                */
  337.                 hasGrowBox,                /* Grow box if TRUE                        */
  338.                 TRUE,                    /* Horizontal scroll                    */
  339.                 TRUE                    /* Vertical scroll                        */
  340.             );
  341.         if (theList == NULL)
  342.             goto exit;
  343.         LIST.selFlags = SELECTION_FLAGS;
  344.         LIST.refCon = 0;                /* Paranoia                                */
  345.         LIST.userHandle = NewHandleClear(sizeof (TwistDownPrivateRecord));
  346.         if (LIST.userHandle == NULL)
  347.             goto failure;
  348.         privatePtr = (TwistDownPrivatePtr) (*LIST.userHandle);
  349. #define PRIV (*privatePtr)
  350.         PRIV.drawProc = (drawProc != NULL) ? drawProc : DefaultTwistDownDrawProc;
  351.         PRIV.tabIndent = tabIndent;
  352.         PRIV.canHiliteSelection = canHiliteSelection;
  353.         PRIV.isLeftJustify = isLeftJustify;
  354.         PRIV.fontNumber = currentPort->txFont;
  355.         PRIV.fontSize = currentPort->txSize;
  356.         /*
  357.          * Grab the horizontal scrollbar and clear it from the list record then
  358.          * link the horizontal scrollbar back to the list, and configure it.
  359.          */
  360.         PRIV.hScroll = LIST.hScroll;
  361.         LIST.hScroll = NULL;
  362.         SetControlReference(PRIV.hScroll, (long) theList);
  363.         AdjustHorizontalScrollbar(theList);
  364. #undef PRIV
  365.         CreateTwistDownButtons(theList);
  366.         goto exit;
  367. failure:
  368.         DisposeTwistDownList(theList);
  369.         theList = NULL;
  370. exit:    return (theList);
  371. }
  372.  
  373. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  374.  * DisposeTwistDownList
  375.  *
  376.  * Dispose of the list and our private data.
  377.  */
  378. pascal void
  379. DisposeTwistDownList(
  380.         ListHandle                theList
  381.     )
  382. {
  383.         if (theList != NULL) {
  384.             if (LIST.userHandle != NULL) {
  385.                 LIST.hScroll = PRIVATE.hScroll;
  386.                 ForgetPoly(PRIVATE.openTriangle);
  387.                 ForgetPoly(PRIVATE.closedTriangle);
  388.                 ForgetPoly(PRIVATE.intermediateTriangle);
  389.                 DisposeHandle((Handle) LIST.userHandle);
  390.                 LIST.userHandle = NULL;
  391.             }
  392.             LDispose(theList);
  393.         }
  394. }
  395.  
  396. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  397.  * SetTwistDownListFont
  398.  *
  399.  * Change the display font and font size
  400.  */
  401. pascal void
  402. SetTwistDownListFont(
  403.         ListHandle                theList,
  404.         short                    fontNumber,
  405.         short                    fontSize
  406.     )
  407. {
  408.         FontInfo                info;
  409.         Rect                    listRect;
  410.         Point                    cellSize;
  411.         
  412.         PRIVATE.fontNumber = fontNumber;
  413.         PRIVATE.fontSize = fontSize;
  414.         SetPort(LIST.port);
  415.         TextFont(PRIVATE.fontNumber);
  416.         TextSize(PRIVATE.fontSize);
  417.         GetFontInfo(&info);
  418.         listRect = LIST.rView;
  419.         cellSize.h = width(listRect);
  420.         cellSize.v = info.ascent + info.descent + info.leading;
  421.         LIST.indent.v = info.ascent;
  422.         LCellSize(cellSize, theList);
  423.         CreateTwistDownButtons(theList);
  424.         InvalRect(&listRect);
  425. }
  426.  
  427. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  428.  * UpdateTwistDownList
  429.  * Explicitly update the twistdown list. This is generally called only after
  430.  * a Modal Dialog or Alert obscured the window.
  431.  */
  432. pascal void
  433. UpdateTwistDownList(
  434.         ListHandle                theList
  435.     )
  436. {
  437.         WindowPtr                theWindow;
  438.         GrafPtr                    savePort;
  439.         Rect                    viewRect;
  440.         RgnHandle                listRgn;
  441.         RgnHandle                clipRgn;
  442.         
  443.         if (theList != NULL) {
  444.             theWindow = (WindowPtr) LIST.port;
  445.             if (EmptyRgn(((WindowPeek) theWindow)->updateRgn) == FALSE) {
  446.                 viewRect = LIST.rView;
  447.                 if (LIST.hScroll != NULL || PRIVATE.hScroll != NULL)
  448.                     viewRect.bottom += kScrollBarWidth;
  449.                 if (LIST.vScroll != NULL)
  450.                     viewRect.right += kScrollBarWidth;
  451.                 listRgn = NewRgn();
  452.                 RectRgn(listRgn, &viewRect);
  453.                 SectRgn(listRgn, ((WindowPeek) theWindow)->updateRgn, listRgn);
  454.                 if (EmptyRgn(listRgn) == FALSE) {
  455.                     /*
  456.                      * Fake an update event handler
  457.                      */
  458.                     GetPort(&savePort);
  459.                     SetPort(theWindow);
  460.                     clipRgn = NewRgn();
  461.                     GetClip(clipRgn);
  462.                     SetClip(listRgn);
  463.                     EraseRgn(listRgn);
  464.                     DrawControls(theWindow);
  465.                     DrawGrowIcon(theWindow);
  466.                     InsetRect(&viewRect, -1, -1);
  467.                     FrameRect(&viewRect);
  468.                     LUpdate(listRgn, theList);
  469.                     DisposeRgn(clipRgn);
  470.                     ValidRgn(listRgn);
  471.                     SetPort(savePort);
  472.                 }
  473.                 DisposeRgn(listRgn);
  474.             }
  475.         }
  476. }
  477.  
  478. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  479.  * MoveTwistDownList
  480.  * Move the list within the window.
  481.  */
  482. pascal void
  483. MoveTwistDownList(
  484.         ListHandle                theList,
  485.         short                    leftEdge,
  486.         short                    topEdge
  487.     )
  488. {
  489.         Rect                    viewRect;
  490.         
  491.         if (LIST.rView.left != leftEdge || LIST.rView.top != topEdge) {
  492.             viewRect = LIST.rView;
  493.             InsetRect(&viewRect, -1, -1);
  494.             InvalRect(&viewRect);
  495.             OffsetRect(
  496.                 &LIST.rView,
  497.                 leftEdge - LIST.rView.left,
  498.                 topEdge - LIST.rView.top
  499.             );
  500.             viewRect = LIST.rView;
  501.             InsetRect(&viewRect, -1, -1);
  502.             InvalRect(&viewRect);
  503.             MoveControl(
  504.                 LIST.vScroll,
  505.                 LIST.rView.right - kScrollBarOffset,
  506.                 LIST.rView.top - 1
  507.             );
  508.             MoveControl(
  509.                 PRIVATE.hScroll,
  510.                 LIST.rView.left - 1,
  511.                 LIST.rView.bottom - kScrollBarOffset
  512.             );
  513.         }
  514. }
  515.  
  516. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  517.  * SizeTwistDownList
  518.  * Resize the list. The viewRect is the list rectangle size and does not include
  519.  * the scrollbars. To create a full-screen list, pass the window's portRect with
  520.  * the scrollbars removed.
  521.  */
  522. pascal void
  523. SizeTwistDownList(
  524.         ListHandle                theList,
  525.         short                    newWidth,
  526.         short                    newHeight
  527.     )
  528. {
  529.         Rect                    viewRect;
  530.         Point                    cellSize;
  531.         GrafPtr                    currentPort;
  532.         GrafPtr                    listPort;
  533.  
  534.         GetPort(¤tPort);
  535.         listPort = LIST.port;
  536.         SetPort(listPort);
  537.         viewRect = LIST.rView;
  538.         InsetRect(&viewRect, -1, -1);
  539.         InvalRect(&viewRect);
  540.         /*
  541.          * Put the horizontal scrollbar back into the list record so that
  542.          * the list manager does the resizing for us.
  543.          */
  544.         LIST.hScroll = PRIVATE.hScroll;
  545.         LSize(newWidth, newHeight, theList);
  546.         LIST.hScroll = NULL;
  547.         cellSize = LIST.cellSize;
  548.         cellSize.h = width(LIST.rView);
  549.         LCellSize(cellSize, theList);
  550.         AdjustHorizontalScrollbar(theList);
  551.         viewRect = LIST.rView;
  552.         InsetRect(&viewRect, -1, -1);
  553.         InvalRect(&viewRect);
  554.         SetPort(currentPort);
  555. }
  556.  
  557. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  558.  * CreateTwistDownButtons
  559.  *
  560.  * CreateTwistDownButtons
  561.  * This function creates the three states of the twist-down button that are drawn
  562.  * in the display list.
  563.  *
  564.  * CreateTwistDownButtons is called when the list is created. The application must
  565.  * call it directly after changing the list cell height (by calling LSize).
  566.  *
  567.  * Note that the port and text drawing characteristics must have been set.
  568.  */
  569. pascal void
  570. CreateTwistDownButtons(
  571.         ListHandle                theList
  572.     )
  573. {
  574.         short                    buttonSize;
  575.         short                    halfSize;
  576.         short                    intermediateSize;
  577.         FontInfo                info;
  578.  
  579.         ForgetPoly(PRIVATE.openTriangle);
  580.         ForgetPoly(PRIVATE.closedTriangle);
  581.         ForgetPoly(PRIVATE.intermediateTriangle);
  582.         GetFontInfo(&info);
  583.         buttonSize = info.ascent;                /* The "show sublist" button    */
  584.         buttonSize &= ~1;                        /* Round down to an even number    */
  585.         halfSize = buttonSize / 2;
  586.         intermediateSize = (buttonSize * 3) / 4;
  587.         PRIVATE.openTriangle = OpenPoly();
  588.             MoveTo(0, halfSize);
  589.             LineTo(buttonSize, halfSize);
  590.             LineTo(halfSize, buttonSize);
  591.             LineTo(0, halfSize);
  592.         ClosePoly();
  593.         if (PRIVATE.isLeftJustify) {            /* Roman alphabet triangles        */
  594.             PRIVATE.closedTriangle = OpenPoly();
  595.                 MoveTo(halfSize, 0);
  596.                 LineTo(buttonSize, halfSize);
  597.                 LineTo(halfSize, buttonSize);
  598.                 LineTo(halfSize, 0);
  599.             ClosePoly();
  600.             PRIVATE.intermediateTriangle = OpenPoly();    
  601.                 MoveTo(intermediateSize, 0);
  602.                 LineTo(intermediateSize, intermediateSize);
  603.                 LineTo(0, intermediateSize);
  604.                 LineTo(intermediateSize, 0);
  605.             ClosePoly();
  606.         }
  607.         else {                                    /* Arabic/Hebrew triangles        */
  608.             PRIVATE.closedTriangle = OpenPoly();
  609.                 MoveTo(buttonSize - halfSize, 0);
  610.                 LineTo(0, halfSize);
  611.                 LineTo(buttonSize - halfSize, buttonSize);
  612.                 LineTo(buttonSize - halfSize, 0);
  613.             ClosePoly();
  614.             PRIVATE.intermediateTriangle = OpenPoly();
  615.                 MoveTo(buttonSize - intermediateSize, 0);
  616.                 LineTo(buttonSize - intermediateSize, intermediateSize);
  617.                 LineTo(buttonSize, intermediateSize);
  618.                 LineTo(buttonSize - intermediateSize, 0);
  619.             ClosePoly();
  620.         }        
  621.         /*
  622.          * Remember the width of the "button" area.
  623.          */
  624.         PRIVATE.triangleWidth =
  625.                     (**PRIVATE.openTriangle).polyBBox.right
  626.                     + kTriangleOutsideGap
  627.                     + kTriangleInsideGap;
  628. }
  629.  
  630. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  631.  * DoTwistDownClick
  632.  *
  633.  * DoTwistDownClick handles all processing after a click in the list window.
  634.  * It returns an indication of the user action:
  635.  *    kTwistDownNotInList
  636.  *        The click was not in this list. Your application may ignore this click or
  637.  *        take other appropriate action.
  638.  *    kTwistDownNoClick
  639.  *        The user released the mouse outside of the list area: this click should be
  640.  *        ignored. (It may have been a click in the scroll bar.)
  641.  *    kTwistDownButtonClick
  642.  *        The user clicked on the twist-down button. The list will be expanded or
  643.  *        contracted as appropriate.
  644.  *    kTwistDownClick
  645.  *        The user clicked (once) on a list datum. The application should treat this
  646.  *        as an item selection.
  647.  *    kTwistDownDoubleClick
  648.  *        The user double-clicked on a list datum. The application should open this
  649.  *        item or take other appropriate action.
  650.  *
  651.  * If DoTwistDownClick returns kTwistDownButtonClick, kTwistDownClick, or
  652.  * kTwistDownDoubleClick, selectedListCell will be set to the cell that the user
  653.  * clicked on.
  654.  */
  655. pascal TwistDownClickState
  656. DoTwistDownClick(
  657.         ListHandle                theList,
  658.         const EventRecord        *eventRecordPtr,
  659.         Cell                    *selectedListCell
  660.     )
  661. {
  662.         Cell                    theCell;
  663.         Rect                    hitRect;
  664.         Boolean                    inHitRect;
  665.         Boolean                    newInHitRect;
  666.         short                    cellHeight;
  667.         short                    visibleTop;
  668.         TwistDownHdl            twistDownHandle;
  669.         Point                    mousePt;
  670.         TwistDownClickState        result;
  671.         long                    finalTicks;
  672.         short                    part;
  673.         ControlHandle            theControl;
  674.         static ControlActionUPP    controlActionUPP;
  675.         
  676.         mousePt = eventRecordPtr->where;
  677.         GlobalToLocal(&mousePt);
  678.         /*
  679.          * We handle the horizontal scrollbar ourselves -- and do not pass
  680.          * these clicks to LClick as the scrollbar isn't there anymore.
  681.          */
  682.         hitRect = (**PRIVATE.hScroll).contrlRect;
  683.         if (PtInRect(mousePt, &hitRect)) {
  684.             part = FindControl(
  685.                         mousePt,
  686.                         (**PRIVATE.hScroll).contrlOwner,
  687.                         &theControl
  688.                     );
  689.             if (part >= 0 && theControl == PRIVATE.hScroll) {
  690.                 if (part == kControlIndicatorPart)        // use new name  dbt 4/24/96
  691.                 {
  692.                     if (TrackControl(theControl, mousePt, NULL))
  693.                         ScrollTwistDownList(theControl);
  694.                 }
  695.                 else {
  696.                     if (controlActionUPP == NULL) {
  697.                         controlActionUPP = NewControlActionProc(
  698.                                     ScrollTwistDownActionProc
  699.                                 );
  700.                     }
  701.                     TrackControl(theControl, mousePt, controlActionUPP);
  702.                 }
  703.             }        
  704.         }
  705.         else {
  706.             hitRect = LIST.rView;
  707.             hitRect.right += kScrollBarWidth;
  708.             if (PtInRect(mousePt, &hitRect) == FALSE)
  709.                 result = kTwistDownNotInList;
  710.             else {
  711.                 /*
  712.                  * Set hitRect to the area of the list that contains the
  713.                  * twist-down triangle button. Note that this presumes a list
  714.                  * where only column zero is displayed.
  715.                  */
  716.                 if (PRIVATE.isLeftJustify) {
  717.                     hitRect.right = LIST.rView.left
  718.                             + LIST.indent.h
  719.                             + PRIVATE.triangleWidth;
  720.                 }
  721.                 else {
  722.                     hitRect.left = LIST.rView.right
  723.                             - LIST.indent.h
  724.                             - PRIVATE.triangleWidth;
  725.                 }
  726.                 inHitRect = FALSE;
  727.                 if (PtInRect(mousePt, &hitRect)) {
  728.                     /*
  729.                      * It's in a the triangle area. Find the selected cell and
  730.                      * check whether this cell's element has a visible button.
  731.                      */
  732.                     visibleTop = LIST.visible.top;
  733.                     cellHeight = LIST.cellSize.v;
  734.                     theCell.h = 0;        /* This has the visual content            */
  735.                     theCell.v =
  736.                         ((mousePt.v - LIST.rView.top) / cellHeight)
  737.                         + visibleTop;
  738.                     /*
  739.                      * Set inHitRect TRUE if there is a sub-list button here.\
  740.                      * Note: it is possible to have a button but no actual
  741.                      * sub-list (consider an empty folder in a disk hierarchy: the
  742.                      * presence of the button tells the user "it's a folder").
  743.                      */
  744.                     twistDownHandle = GetTwistDownElementHandle(theList, theCell);
  745.                     if (twistDownHandle != NULL
  746.                      && TestTDFlag(twistDownHandle, kHasTwistDown))
  747.                         inHitRect = TRUE;
  748.                 }
  749.                 if (inHitRect == FALSE) {
  750.                     /*
  751.                      * This cell doesn't have an expansion triangle, or the user
  752.                      * did not click in the button area. Just call the  normal
  753.                      * list click handler to manage the scroll bars. Set result
  754.                      * appropriately.
  755.                      */
  756.                     result = (LClick(mousePt, eventRecordPtr->modifiers, theList))
  757.                         ? kTwistDownDoubleClick
  758.                         : kTwistDownClick;
  759.                 }
  760.                 else {
  761.                     /*
  762.                      * The user clicked on in the twist-down button area. Simulate
  763.                      * a button click and track the mouse as it wanders in and out
  764.                      * of the button area. (inHitRect is true at this point).
  765.                      * Whenever the button selection state changes, call LDraw to
  766.                      * redraw the twist-down triangle. This is the way to simulate
  767.                      * TrackControl.
  768.                      */
  769.                     SetTDFlag(twistDownHandle,
  770.                         (kDrawButtonFilled | kOnlyRedrawButton));
  771.                     LDraw(theCell, theList);
  772.                     /*
  773.                      * Set hitRect to the dimensions of the twist-down button.
  774.                      */
  775.                     hitRect.top =
  776.                         ((theCell.v - visibleTop) * cellHeight)
  777.                         + LIST.rView.top;
  778.                     hitRect.bottom = hitRect.top + cellHeight;
  779.                     /*
  780.                      * Track the mouse while it still down: if it moves into the
  781.                      * triangle rectangle, redraw it filled, if it moves out of
  782.                      * the triangle, redraw it unfilled.
  783.                      */
  784.                     if (StillDown()) {
  785.                         while (WaitMouseUp()) {
  786.                             GetMouse(&mousePt);
  787.                             newInHitRect = PtInRect(mousePt, &hitRect);
  788.                             if (newInHitRect != inHitRect) {
  789.                                 /*
  790.                                  * The mouse moved in or out of the triangle.
  791.                                  */
  792.                                 InvertTDFlag(twistDownHandle, kDrawButtonFilled);
  793.                                 LDraw(theCell, theList);
  794.                                 inHitRect = newInHitRect;
  795.                             }
  796.                         }
  797.                     }
  798.                     /*
  799.                      * The user released the mouse.
  800.                      */
  801.                     if (inHitRect == FALSE) {
  802.                         /*
  803.                          * Normally, drawButtonFilled will be clear. It can be set,
  804.                          * however, if the user clicks so briefly on the triangle
  805.                          * that the StillDown() test above is FALSE.
  806.                          */
  807.                         if (TestTDFlag(twistDownHandle, kDrawButtonFilled)) {
  808.                             ClearTDFlag(twistDownHandle, kDrawButtonFilled);
  809.                             LDraw(theCell, theList);
  810.                         }
  811.                         result = kTwistDownNoClick;
  812.                     }
  813.                     else {
  814.                         /*
  815.                          * The user released the mouse in the expansion triangle.
  816.                          * Draw an intermediate "animation" triangle. Then call 
  817.                          * ExpandOrCollapseTwistDownList which will redraw the
  818.                          * button in its new state.
  819.                          */
  820.                         SetTDFlag(twistDownHandle,
  821.                             (kDrawIntermediate | kEraseButtonArea));
  822.                         LDraw(theCell, theList);
  823.                         Delay(kAnimationDelay, &finalTicks);
  824.                         ClearTDFlag(
  825.                             twistDownHandle,
  826.                             (  kDrawIntermediate
  827.                              | kDrawButtonFilled
  828.                              | kEraseButtonArea
  829.                             )
  830.                         );
  831.                         ExpandOrCollapseTwistDownList(theList, theCell);
  832.                         result = kTwistDownButtonClick;
  833.                         *selectedListCell = theCell;
  834.                     }
  835.                     ClearTDFlag(twistDownHandle, kOnlyRedrawButton);
  836.                 }
  837.             }
  838.         }
  839.         return (result);
  840. }
  841.  
  842. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  843.  * ExpandOrCollapseTwistDownList
  844.  *
  845.  * ExpandOrCollapseTwistDownList modifies the "show sublist" flag for the selected
  846.  * cell and rebuilds the visual display.
  847.  */
  848. pascal void
  849. ExpandOrCollapseTwistDownList(
  850.         ListHandle                theList,
  851.         Cell                    selectedListCell
  852.     )
  853. {
  854.         TwistDownHdl            twistDownHandle;
  855.         
  856.         twistDownHandle = GetTwistDownElementHandle(theList, selectedListCell);
  857.         if (twistDownHandle != NULL
  858.          && TestTDFlag(twistDownHandle, kHasTwistDown)) {
  859.             InvertTDFlag(twistDownHandle, kShowSublist);
  860.             /*
  861.              * Redraw the twist-down button in its new state.
  862.              */
  863.             ClearTDFlag(twistDownHandle, kDrawButtonFilled);
  864.             SetTDFlag(twistDownHandle, (kOnlyRedrawButton | kEraseButtonArea));
  865.             LDraw(selectedListCell, theList);
  866.             ClearTDFlag(twistDownHandle, (kOnlyRedrawButton | kEraseButtonArea));
  867.             /*
  868.              * If some other part of the list will change, rebuild the
  869.              * List Manager list cells and redraw the list.
  870.              */
  871.             if ((**twistDownHandle).subElement != NULL)
  872.                 BuildVisibleList(theList, selectedListCell.v);
  873.         }
  874. }
  875.  
  876. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  877.  * CreateVisibleList
  878.  *
  879.  * CreateVisibleList is called when the list is created. It stores the list
  880.  * head into cell [0, 0] and calls BuildVisibleList to instantiate the display.
  881.  */
  882. pascal void
  883. CreateVisibleList(
  884.         ListHandle                theList,
  885.         TwistDownHdl            twistDownHandle
  886.     )
  887. {
  888.         Cell                    theCell;
  889.         
  890.         if (LIST.dataBounds.bottom == 0) {
  891.             /*
  892.              * Add one row to the list so there is a place for the head element.
  893.              */
  894.             LSetDrawingMode(FALSE, theList);
  895.             LAddRow(1, 0, theList);
  896.             LSetDrawingMode(TRUE, theList);
  897.         }
  898.         SetPt(&theCell, 0, 0);
  899.         LSetCell(&twistDownHandle, sizeof twistDownHandle, theCell, theList);
  900.         BuildVisibleList(theList, 0);
  901. }
  902.  
  903. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  904.  * BuildVisibleList
  905.  *
  906.  * BuildVisibleList is called when the list is created, or when the user clicks
  907.  * on a twist-down button. selectedRow is the first row that needs to be redrawn.
  908.  * To rebuild the entire list, store the list head into cell [0, 0] and call
  909.  * with selectedRow = 0. Note: because people can be rebuilding the list from
  910.  * within a larger sublist context, the list head must be stored in cell [0, 0].
  911.  */
  912. pascal void
  913. BuildVisibleList(
  914.         ListHandle                theList,
  915.         short                    selectedRow
  916.     )
  917. {
  918.         short                    nRows;            /* How many we need    to show        */
  919.         short                    currentRows;    /* How many are in the list        */
  920.         Rect                    viewRect;
  921.         TwistDownHdl            listHead;
  922.         Cell                    theCell;
  923.         
  924.         LSetDrawingMode(FALSE, theList);
  925.         SetPt(&theCell, 0, 0);                        /* Get the list head        */
  926.         listHead = GetTwistDownElementHandle(theList, theCell);
  927.         ClearSelectedElementBit(listHead);
  928.         CopySelectionStateToList(theList, selectedRow);
  929.         nRows = CountVisibleElements(listHead);
  930.         currentRows = LIST.dataBounds.bottom;
  931.         if (currentRows > nRows)
  932.             LDelRow(currentRows - nRows, nRows, theList);    /* Shrink the list    */
  933.         else if (currentRows < nRows)
  934.             LAddRow(nRows - currentRows, currentRows + 1, theList);    /* Grow it    */
  935.         if (nRows != 0)
  936.             SetElementsInList(theList, listHead, &theCell);
  937.         LSetDrawingMode(TRUE, theList);
  938.         /*
  939.          * Redraw any elements that are greater than the inserted row.
  940.          */
  941.         viewRect = LIST.rView;
  942.         viewRect.top += ((selectedRow + 1) - LIST.visible.top)
  943.                     * LIST.cellSize.v;
  944.         if (viewRect.top < viewRect.bottom)
  945.             InvalRect(&viewRect);
  946. }
  947.  
  948. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  949.  * CountVisibleElements
  950.  *
  951.  * CountVisibleElements is a recursive function that returns the number of visible
  952.  * elements in the list. Call with the list head to process the entire list. Note:
  953.  * list elements are visible, but sub-lists are visible only if the button state
  954.  * requests visiblity. This function may be called with a NULL argument without
  955.  * problems. Also note that we ignore the current List Manager cell data, working
  956.  * only with the actual linked list structure. By convention, however, the list
  957.  * head is stored in cell [0, 0].
  958.  */
  959. static short
  960. CountVisibleElements(
  961.         TwistDownHdl            twistDownHandle
  962.     )
  963. {
  964.         short                    result;
  965.  
  966.         result = 0;
  967.         while (twistDownHandle != NULL) {
  968.             ++result;
  969.             if (TestTDFlag(twistDownHandle, kShowSublist))
  970.                 result += CountVisibleElements((**twistDownHandle).subElement);
  971.             twistDownHandle = (**twistDownHandle).nextElement;
  972.         }            
  973.         return (result);
  974. }
  975.  
  976. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  977.  * CountListElements
  978.  *
  979.  * CountListElements returns the number of elements in the list: it counts all
  980.  * elements in all sub-lists, visible or not.
  981.  */
  982. pascal unsigned long
  983. CountListElements(
  984.         TwistDownHdl            twistDownHandle
  985.     )
  986. {
  987.         unsigned long            result;
  988.  
  989.         result = 0;
  990.         while (twistDownHandle != NULL) {
  991.             ++result;
  992.             if ((**twistDownHandle).subElement != NULL)
  993.                 result += CountVisibleElements((**twistDownHandle).subElement);
  994.             twistDownHandle = (**twistDownHandle).nextElement;
  995.         }            
  996.         return (result);
  997. }
  998.  
  999. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1000.  * ClearSelectedElementBit
  1001.  *
  1002.  * When we expand or contract the visible list, the relationship between the
  1003.  * visual display and the actual data changes. In particular, the selected cell
  1004.  * may contain different data. To resolve this dilemna, we copy the current
  1005.  * selection from the display list to the data elements, then copy the information
  1006.  * back when the new elements are inserted into the list. This is a three-step
  1007.  * process. First, we clear out the kSelectedElement bit from the linked list.
  1008.  */
  1009. static void
  1010. ClearSelectedElementBit(
  1011.         TwistDownHdl            twistDownHandle
  1012.     )
  1013. {
  1014.         while (twistDownHandle != NULL) {
  1015.             ClearTDFlag(twistDownHandle, kSelectedElement);
  1016.             if ((**twistDownHandle).subElement != NULL)
  1017.                 ClearSelectedElementBit((**twistDownHandle).subElement);
  1018.             twistDownHandle = (**twistDownHandle).nextElement;
  1019.         }            
  1020. }
  1021.  
  1022. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1023.  * CopySelectionStateToList
  1024.  *
  1025.  * After clearing the kSelectedElementBit from the element list, we save the
  1026.  * "is selected" bit from the display list into the associated element. Note that
  1027.  * the cell will be deselected but selection state is restored when the list is
  1028.  * rebuilt. The function starts with the row after the selected (clicked on) cell
  1029.  * as that cell is not redrawn and, presumably, is correctly hilited.
  1030.  */
  1031. static void
  1032. CopySelectionStateToList(
  1033.         ListHandle                theList,
  1034.         short                    selectedRow
  1035.     )
  1036. {
  1037.         TwistDownHdl            twistDownHandle;
  1038.         Cell                    theCell;
  1039.         
  1040.         SetPt(&theCell, 0, selectedRow + 1);
  1041.         while (LGetSelect(TRUE, &theCell, theList)) {
  1042.             twistDownHandle = GetTwistDownElementHandle(theList, theCell);
  1043.             if (twistDownHandle != NULL)
  1044.                 SetTDFlag(twistDownHandle, kSelectedElement);
  1045.             LSetSelect(FALSE, theCell, theList);
  1046.             ++theCell.v;
  1047.         }
  1048. }
  1049.  
  1050. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1051.  * SetElementsInList
  1052.  *
  1053.  * This is a recursive function that copies visible list elements from the linked
  1054.  * list to the List Manager list. The List Manager list was extended so it holds
  1055.  * all visible cells. Note that currentCell is a "global" that always contains
  1056.  * the current List Manager cell. To process the entire list, set currentCell to
  1057.  * [0, 0] and call with the list head. If the list element is selected (from
  1058.  * CopySelectionStateToList above), select this list cell.
  1059.  */ 
  1060. static void
  1061. SetElementsInList(
  1062.         ListHandle                theList,
  1063.         TwistDownHdl            twistDownHandle,
  1064.         Cell                    *currentCell
  1065.     )
  1066. {
  1067.         while (twistDownHandle != NULL) {
  1068.             LSetCell(
  1069.                 &twistDownHandle, sizeof twistDownHandle, *currentCell, theList);
  1070.             if (TestTDFlag(twistDownHandle, kSelectedElement))
  1071.                 LSetSelect(TRUE, *currentCell, theList);
  1072.             ++currentCell->v;
  1073.             if (TestTDFlag(twistDownHandle, kShowSublist)) {
  1074.                 SetElementsInList(
  1075.                     theList,
  1076.                     (**twistDownHandle).subElement,
  1077.                     currentCell
  1078.                 );
  1079.             }
  1080.             twistDownHandle = (**twistDownHandle).nextElement;
  1081.         }
  1082. }
  1083.  
  1084. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1085.  * GetTwistDownElementHandle
  1086.  *
  1087.  * GetTwistDownElementHandle returns the TwistDownHdl that is stored in a List
  1088.  * cell. It will return NULL if the cell is out of bounds.
  1089.  */
  1090. pascal TwistDownHdl
  1091. GetTwistDownElementHandle(
  1092.         ListHandle                theList,
  1093.         Cell                    theCell
  1094.     )
  1095. {
  1096.         TwistDownHdl            twistDownHandle;
  1097.         short                    dataSize;
  1098.         
  1099.         dataSize = sizeof twistDownHandle;
  1100.         LGetCell(&twistDownHandle, &dataSize, theCell, theList);
  1101.         if (dataSize != sizeof twistDownHandle)
  1102.             twistDownHandle = NULL;
  1103.         return (twistDownHandle);
  1104. }
  1105.  
  1106. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1107.  * NewTwistDownSiblingSet
  1108.  *
  1109.  * Initialize a sibling set.
  1110.  */
  1111. pascal void
  1112. NewTwistDownSiblingSet(
  1113.         TwistDownSiblingSetPtr    twistDownSiblingSetPtr
  1114.     )
  1115. {
  1116.         SIBLING.thisElement = NULL;
  1117.         SIBLING.firstElement = NULL;
  1118.         SIBLING.previousElement = NULL;
  1119. }
  1120.  
  1121. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1122.  * MakeTwistDownSibling
  1123.  *
  1124.  * Add a new element (by calling MakeTwistDownElement) to the end of the current
  1125.  * linked list. Also, handle the bookkeeping needed for the first element.
  1126.  */
  1127. pascal OSErr
  1128. MakeTwistDownSibling(
  1129.         TwistDownSiblingSetPtr    twistDownSiblingSetPtr,
  1130.         short                    indentLevel,
  1131.         unsigned short            dataLength,
  1132.         const Ptr                dataPtr
  1133.     )
  1134. {
  1135.         OSErr                    status;
  1136.         
  1137.         status = MakeTwistDownElement(
  1138.                     SIBLING.previousElement,
  1139.                     indentLevel,
  1140.                     dataLength,
  1141.                     dataPtr,
  1142.                     &SIBLING.thisElement
  1143.                 );
  1144.         if (status == noErr) {
  1145.             SIBLING.previousElement = SIBLING.thisElement;
  1146.             if (SIBLING.firstElement == NULL)
  1147.                 SIBLING.firstElement = SIBLING.thisElement;
  1148.         }
  1149.         return (status);
  1150. }
  1151.  
  1152. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1153.  * MakeTwistDownElement
  1154.  *
  1155.  * Adds an element to a linked list. It is designed to create elements in a
  1156.  * hierarchical linked-list where each element may be followed by a successor
  1157.  * (on the same "level") and/or by a child list (on a lower "level"). Note that
  1158.  * MakeTwistDownElement is only concerned with the linked list. It does not
  1159.  * change the List Manager list.
  1160.  *
  1161.  * The parameters are as follows:
  1162.  *    previousElement
  1163.  *        This is a handle to the predecessor to this element. previousElement is
  1164.  *        NULL If this is the first element in a list or the first on a level.
  1165.  *    indentLevel
  1166.  *        This is the indentation-level of this list element. It is only
  1167.  *        used to tab the list elements on the visual display. If you don't
  1168.  *        want tabbing, set tabIndent to zero when the list was created.
  1169.  *    dataLength
  1170.  *        This is the length of the list element datum.
  1171.  *    dataPtr
  1172.  *        This is a pointer to the first byte of the list element datum. If NULL,
  1173.  *        a data block of the requisite size will be created, but the caller is
  1174.  *        responsible for filling it in.
  1175.  *    result
  1176.  *        If MakeElement succeeds, result will will contain a handle to the
  1177.  *        list element it created. This is needed to create a successor
  1178.  *        element.
  1179.  */
  1180. pascal OSErr
  1181. MakeTwistDownElement(
  1182.         TwistDownHdl            previousElement,
  1183.         short                    indentLevel,
  1184.         unsigned short            dataLength,
  1185.         const Ptr                dataPtr,
  1186.         TwistDownHdl            *result
  1187.     )
  1188. {
  1189.         TwistDownHdl            twistDownHandle;
  1190.         
  1191.         twistDownHandle = (TwistDownHdl) NewHandle(
  1192.                     sizeof (TwistDownRecord)
  1193.                     - sizeof (unsigned char)
  1194.                     + dataLength
  1195.                 );
  1196.         if (twistDownHandle != NULL) {
  1197.             if (previousElement != NULL)
  1198.                 (**previousElement).nextElement = twistDownHandle;
  1199.             (**twistDownHandle).nextElement = NULL;
  1200.             (**twistDownHandle).subElement = NULL;
  1201.             (**twistDownHandle).flag = 0;
  1202.             (**twistDownHandle).indentLevel = indentLevel;
  1203.             (**twistDownHandle).dataLength    = dataLength;
  1204.             if (dataPtr != NULL)
  1205.                 BlockMoveData(dataPtr, (**twistDownHandle).data, dataLength);
  1206.             *result = twistDownHandle;
  1207.         }
  1208.         return (MemError());
  1209. }
  1210.  
  1211. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1212.  * DisposeTwistDownHdl
  1213.  *
  1214.  * Dispose of the linked list that has the argument at its head, and of all
  1215.  * sublists linked to this list. Note that it presumes that the list element does
  1216.  * not, itself, contain Ptr or Handle data that must be disposed. The userProc,
  1217.  * if provided, will be called on each element.
  1218.  */
  1219. pascal void
  1220. DisposeTwistDownHdl(
  1221.         TwistDownHdl            twistDownHandle,
  1222.         DisposeTwistDownCallback userProc,
  1223.         void                    *userData
  1224.     )
  1225. {
  1226.         TwistDownHdl            nextElement;
  1227.         TwistDownHdl            subElement;
  1228.         
  1229.         while (twistDownHandle != NULL) {
  1230.             nextElement = (**twistDownHandle).nextElement;
  1231.             subElement = (**twistDownHandle).subElement;
  1232.             if (userProc != NULL)
  1233.                 (*userProc)(twistDownHandle, userData);
  1234.             DisposeHandle((Handle) twistDownHandle);
  1235.             if (subElement != NULL)
  1236.                 DisposeTwistDownHdl(subElement, userProc, userData);
  1237.             twistDownHandle = nextElement;
  1238.         }
  1239. }
  1240.  
  1241. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1242.  * AppendTwistDownList
  1243.  *
  1244.  * Append the second list to the first list. Return the first list.
  1245.  */
  1246. pascal TwistDownHdl
  1247. AppendTwistDownList(
  1248.         TwistDownHdl            dstTwistDownHdl,
  1249.         TwistDownHdl            srcTwistDownHdl
  1250.     )
  1251. {
  1252.         TwistDownHdl            result;
  1253.         
  1254.         if ((result = dstTwistDownHdl) == NULL)
  1255.             result = srcTwistDownHdl;
  1256.         else {
  1257.             while ((**dstTwistDownHdl).nextElement != NULL)
  1258.                 dstTwistDownHdl = (**dstTwistDownHdl).nextElement;
  1259.             (**dstTwistDownHdl).nextElement = srcTwistDownHdl;
  1260.         }
  1261.         return (result);
  1262. }
  1263.  
  1264. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1265.  * TwistDownLDEF
  1266.  *
  1267.  * Draw the twist-down list cell. Note that we have to draw the expansion button
  1268.  * in one of five states: (expanded/compressed), (normal/selected), and an
  1269.  * animation state.
  1270.  */
  1271. pascal void
  1272. TwistDownLDEF(
  1273.         short                    listMessage,
  1274.         Boolean                    listSelect,
  1275.         Rect                    *listRect,
  1276.         Cell                    listCell,
  1277.         short                    listDataOffset,
  1278.         short                    listDataLen,
  1279.         ListHandle                theList
  1280.     )
  1281. {
  1282.         short                    indent;
  1283.         TwistDownHdl            twistDownHandle;
  1284.         register TwistDownPtr    twistDownPtr;
  1285.         GrafPtr                    grafPtr;
  1286.         short                    saveFontNumber;
  1287.         short                    saveFontSize;
  1288.         short                    cellSize;
  1289.         PolyHandle                polyHandle;
  1290.         Point                    polyPoint;
  1291.         Rect                    viewRect;
  1292.         signed char                elementLockState;
  1293.         Boolean                    onlyRedrawButton;
  1294.         FontInfo                info;
  1295. #define TestFlag(flagBit)    (((*twistDownPtr).flag & (flagBit)) != 0)
  1296.  
  1297.         UNUSED(listDataOffset);
  1298.         switch (listMessage) {
  1299.         case lInitMsg:
  1300.             /*
  1301.              * Initialize the list indentation values. Since the userHandle
  1302.              * (which has the TwistDown private data) hasn't been setup yet,
  1303.              * we let the current font and font size establish the indentation.
  1304.              */
  1305.             LIST.indent.h = 4;
  1306.             GetFontInfo(&info);
  1307.             LIST.indent.v = info.ascent;
  1308.             break;
  1309.         case lCloseMsg:
  1310.             if (LIST.userHandle != NULL) {
  1311.                 ForgetPoly(PRIVATE.openTriangle);
  1312.                 ForgetPoly(PRIVATE.closedTriangle);
  1313.                 ForgetPoly(PRIVATE.intermediateTriangle);
  1314.                 DisposeHandle(LIST.userHandle);
  1315.                 LIST.userHandle = NULL;
  1316.             }
  1317.             break;
  1318.         case lDrawMsg:
  1319.             onlyRedrawButton = FALSE;
  1320.             if (listDataLen > 0 && LIST.userHandle != NULL) {
  1321.                 /*
  1322.                  * Get the cell content. This is the handle that has the list
  1323.                  * element. We check that the userHandle has been setup correctly.
  1324.                  * Note that we don't use LFind (or similar) because the data
  1325.                  * might not be aligned in the list cell storage. Actually, the
  1326.                  * data is aligned as we only store Handles in the cells.
  1327.                  */
  1328.                 cellSize = sizeof twistDownHandle;
  1329.                 LGetCell(&twistDownHandle,    &cellSize, listCell, theList);
  1330.                 if (cellSize == sizeof twistDownHandle
  1331.                  && twistDownHandle != NULL) {
  1332.                     elementLockState = HGetState((Handle) twistDownHandle);
  1333.                     HLock((Handle) twistDownHandle);
  1334.                     twistDownPtr = (*twistDownHandle);
  1335.                     onlyRedrawButton = TestFlag(kOnlyRedrawButton);
  1336.                     viewRect = *listRect;
  1337.                     if (onlyRedrawButton) {
  1338.                         if (PRIVATE.isLeftJustify) {
  1339.                             viewRect.right = viewRect.left
  1340.                                         + LIST.indent.h
  1341.                                         + PRIVATE.triangleWidth;
  1342.                         }
  1343.                         else {
  1344.                             viewRect.left = viewRect.right
  1345.                                     - LIST.indent.h
  1346.                                     - kTriangleOutsideGap
  1347.                                     - PRIVATE.triangleWidth;
  1348.                         }
  1349.                     }
  1350.                     if (onlyRedrawButton == FALSE || TestFlag(kEraseButtonArea))
  1351.                         EraseRect(&viewRect);
  1352.                     if (TestFlag(kHasTwistDown)) {
  1353.                         /*
  1354.                          * Draw the expansion triangle in one of
  1355.                          * its three states.
  1356.                          */
  1357.                         polyPoint.v = listRect->top + 1;
  1358.                         if (PRIVATE.isLeftJustify) {
  1359.                             polyPoint.h = listRect->left
  1360.                                         + LIST.indent.h
  1361.                                         + kTriangleOutsideGap;
  1362.                         }
  1363.                         else {
  1364.                             polyPoint.h = listRect->right
  1365.                                         - LIST.indent.h
  1366.                                         - PRIVATE.triangleWidth
  1367.                                         + kTriangleInsideGap;
  1368.                         }
  1369.                         if (TestFlag(kDrawIntermediate))
  1370.                             polyHandle = PRIVATE.intermediateTriangle;
  1371.                         else if (TestFlag(kShowSublist))
  1372.                             polyHandle = PRIVATE.openTriangle;
  1373.                         else {
  1374.                             polyHandle = PRIVATE.closedTriangle;
  1375.                         }
  1376.                         DrawTriangle(
  1377.                             polyHandle,
  1378.                             polyPoint,
  1379.                             TestFlag(kDrawButtonFilled)
  1380.                         );
  1381.                     }
  1382.                     if (onlyRedrawButton == FALSE
  1383.                      && (*twistDownPtr).dataLength > 0) {
  1384.                         /*
  1385.                          * Indent the text to show the depth of the hierarchy.
  1386.                          */
  1387.                         indent = LIST.indent.h
  1388.                             + PRIVATE.triangleWidth
  1389.                             + (PRIVATE.tabIndent * (*twistDownPtr).indentLevel);
  1390.                         viewRect = *listRect;
  1391.                         /*
  1392.                          * Build a display rectangle for the cell text and set the
  1393.                          * pen to the leftmost position of the text. Note that
  1394.                          * this right-justifies text for Arabic and Hebrew.
  1395.                          */
  1396.                         if (PRIVATE.isLeftJustify)
  1397.                             viewRect.left += indent;
  1398.                         else {
  1399.                             viewRect.right -= indent;
  1400.                         }
  1401.                         GetPort(&grafPtr);
  1402.                         saveFontNumber = grafPtr->txFont;
  1403.                         saveFontSize = grafPtr->txSize;
  1404.                         TextFont(PRIVATE.fontNumber);
  1405.                         TextSize(PRIVATE.fontSize);
  1406.                         (PRIVATE.drawProc)(theList, twistDownPtr, &viewRect);
  1407.                         TextFont(saveFontNumber);
  1408.                         TextSize(saveFontSize);
  1409.                     }                            /* Drawing cell                    */
  1410.                     HSetState((Handle) twistDownHandle, elementLockState);
  1411.                 }                                /* Have list element            */
  1412.             }                                    /* Have cell data                */
  1413.             if (listSelect == FALSE || onlyRedrawButton)
  1414.                 break;
  1415.             /* Continue to do hilite */
  1416.         case lHiliteMsg:
  1417.             if (PRIVATE.canHiliteSelection) {
  1418.                 LMSetHiliteMode(LMGetHiliteMode() & ~(1 << hiliteBit));
  1419.                 viewRect = *listRect;
  1420.                 if (PRIVATE.isLeftJustify)
  1421.                     viewRect.left += (LIST.indent.h * PRIVATE.triangleWidth);
  1422.                 else {
  1423.                     viewRect.right -= (LIST.indent.h * PRIVATE.triangleWidth);
  1424.                 }
  1425.                 InvertRect(&viewRect);
  1426.             }
  1427.             break;
  1428.         }
  1429. #undef TestFlag
  1430. }
  1431.  
  1432. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1433.  * DrawTriangle
  1434.  *
  1435.  * Draw the polygon and fill it so it looks a bit like the Finder. If isSelected
  1436.  * is TRUE, the polygon is always black-filled. Else, on color monitors, a light
  1437.  * gray is chosen.
  1438.  *
  1439.  * DrawTriangle uses the DeviceLoop available with System 7 to draw across
  1440.  * multiple screens.
  1441.  */
  1442.  
  1443. typedef struct TriangleInfo {
  1444.     PolyHandle        polyHandle;
  1445.     Point            polyPoint;
  1446. } TriangleInfo, *TriangleInfoPtr;
  1447.  
  1448. static pascal void            DrawThisTriangle(
  1449.         short                    depth,
  1450.         short                    deviceFlags,
  1451.         GDHandle                targetDevice,
  1452.         TriangleInfoPtr            triangleInfoPtr
  1453.     );
  1454.  
  1455. static void
  1456. DrawTriangle(
  1457.         PolyHandle                polyHandle,
  1458.         Point                    polyPoint,
  1459.         Boolean                    isSelected
  1460.     )
  1461. {
  1462.         TriangleInfo            triangleInfo;
  1463.         RgnHandle                drawingRgn;
  1464.         long                    savedA5;
  1465.         static DeviceLoopDrawingUPP drawingProcUPP;
  1466.         /*
  1467.          * Refresh A5 so we can use the QuickDraw globals, even if we're called
  1468.          * in a non-application context.
  1469.          */
  1470.         savedA5 = SetCurrentA5();
  1471.         triangleInfo.polyHandle = polyHandle;
  1472.         triangleInfo.polyPoint = polyPoint;
  1473.         OffsetPoly(polyHandle, polyPoint.h, polyPoint.v);
  1474.         if (isSelected)
  1475.             FillPoly(polyHandle, &qd.black);
  1476.         else {
  1477.             drawingRgn = NewRgn();
  1478.             OpenRgn();
  1479.             FramePoly(polyHandle);
  1480.             CloseRgn(drawingRgn);
  1481.             if (drawingProcUPP == NULL)
  1482.                 drawingProcUPP = NewDeviceLoopDrawingProc(DrawThisTriangle);
  1483.             DeviceLoop(drawingRgn, drawingProcUPP, (long) &triangleInfo, 0);
  1484.             DisposeRgn(drawingRgn);
  1485.         }
  1486.         /*
  1487.          * A thicker pen might look better for large font sizes, but it needs
  1488.          * to be done in a way that looks good for both button orientations.
  1489.          */
  1490.         FramePoly(polyHandle);
  1491.         OffsetPoly(polyHandle, -polyPoint.h, -polyPoint.v);
  1492.         SetA5(savedA5);
  1493. }
  1494.  
  1495. static pascal void
  1496. DrawThisTriangle(
  1497.         short                    depth,
  1498.         short                    deviceFlags,
  1499.         GDHandle                targetDevice,
  1500.         TriangleInfoPtr            triangleInfoPtr
  1501.     )
  1502. {
  1503.         RGBColor                foreColor;
  1504.         RGBColor                saveForeColor;
  1505.         RGBColor                backColor;
  1506.         short                    i;
  1507.         Rect                    polyRect;
  1508. #define TRI    (*triangleInfoPtr)
  1509.  
  1510.         UNUSED(deviceFlags);
  1511.         UNUSED(targetDevice);
  1512.         polyRect = (**TRI.polyHandle).polyBBox;
  1513.         LocalToGlobal(& ((Point *) &polyRect)[0]);
  1514.         LocalToGlobal(& ((Point *) &polyRect)[1]);
  1515.         if (depth > 1) {
  1516.             /*
  1517.              * We are drawing on a color device (or devices). Fill the unselected
  1518.              * triangle with a very light gray. The Finder extends this by filling
  1519.              * using the icon color instead of black.
  1520.              */
  1521.             GetForeColor(&foreColor);
  1522.             saveForeColor = foreColor;
  1523.             GetBackColor(&backColor);
  1524.             /*
  1525.              * This loop sets foreColor to a very light gray.
  1526.              */
  1527.             for (i = 0; i < 8; i++) {
  1528.                 if (GetGray(GetGDevice(), &backColor, &foreColor) == FALSE)
  1529.                     break;
  1530.             }
  1531.             RGBForeColor(&foreColor);
  1532.             FillPoly(TRI.polyHandle, &qd.black);
  1533.             RGBForeColor(&saveForeColor);
  1534.         }
  1535.         else {
  1536. #if MONOCHROME_FILL
  1537.             /*
  1538.              * We really don't need to do this, but it was useful in debugging
  1539.              * the algorithm on a machine with multiple displays. This fills
  1540.              * the polygon with a light gray texture on monochrome displays.
  1541.              * This is different from the Finder algorithm.
  1542.              */
  1543.             FillPoly(TRI.polyHandle, (ConstPatternParam) &qd.ltGray);
  1544. #else
  1545.             /*
  1546.              * Normally, we need only erase the interior of the polygon.
  1547.              */
  1548.             ErasePoly(TRI.polyHandle);
  1549. #endif
  1550. #undef TRI
  1551.         }
  1552. }
  1553.  
  1554. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1555.  * SortTwistDownList
  1556.  *
  1557.  * Sort a list according to the comparison function. Algorithm adapted from
  1558.  * Numerical Recipes in C (Press, Flannery, Teukolsky, & Vetterling),
  1559.  * ISBN 0-521-35465-X. Algorithm 8.2 "Heapsort"
  1560.  *
  1561.  * This is *not* a recursive procedure. It allocates a temporary array that holds
  1562.  * a oopy of the list. The algorithm, then, is:
  1563.  *    1. Count the number of elements in the list - don't count sublists, though.
  1564.  *    2. Allocate a vector to hold the list handles and copy the list handles
  1565.  *        into the vector.
  1566.  *    3. Heapsort.
  1567.  *    4. The "Promotion" phase creates a new list set.
  1568.  * The function returns noErr on success, or a memory allocation error. By using
  1569.  * a non-recursive function, we avoid a problem where an ordered input list chews
  1570.  * up the application stack, which could lead to bizarre crashes.
  1571.  */
  1572. pascal OSErr
  1573. SortTwistDownList(
  1574.         TwistDownHdl            *twistDownHdlPtr,
  1575.         TwistDownSortCompareProc sortCompareProc,
  1576.         void                    *refCon
  1577.     )
  1578. {
  1579.         OSErr                    status;
  1580.         unsigned long            nElements;
  1581.         unsigned long            i;
  1582.         unsigned long            j;
  1583.         unsigned long            siftIndex;
  1584.         unsigned long            lastIndex;
  1585.         TwistDownHdl            twistDownHdl;
  1586.         TwistDownHdl            *heapVector;    /* One-origin vector        */
  1587.  
  1588.         nElements = 0;
  1589.         status = noErr;
  1590.         for (twistDownHdl = *twistDownHdlPtr;    /* Count list elements        */
  1591.                 twistDownHdl != NULL;
  1592.                 twistDownHdl = (**twistDownHdl).nextElement) {
  1593.             ++nElements;
  1594.         }
  1595.         if (nElements > 1) {            /* 0 or 1 elements aren't sorted    */
  1596.             heapVector = (TwistDownHdl *)
  1597.                         NewPtr((nElements + 1) * sizeof (TwistDownHdl));
  1598.             if (heapVector == NULL)
  1599.                 status = MemError();
  1600.             else {
  1601.                 /*
  1602.                  * Copy the list into the vector. Note that the vector is one-
  1603.                  * origin (this prevents an infinite loop if the index is zero).
  1604.                  */
  1605.                 for (i = 1, twistDownHdl = *twistDownHdlPtr;
  1606.                         twistDownHdl != NULL;
  1607.                         twistDownHdl = (**twistDownHdl).nextElement) {
  1608.                     heapVector[i++] = twistDownHdl;
  1609.                 }
  1610.                 siftIndex = (nElements >> 1) + 1;
  1611.                 lastIndex = nElements;
  1612.                 for (;;) {
  1613.                     if (siftIndex > 1)        /* Still in hiring phase?        */
  1614.                         twistDownHdl = heapVector[--siftIndex];
  1615.                     else {                    /* Else, in promotion phase?    */
  1616.                         /*
  1617.                          * Clear a space at the end of the vector, retire the
  1618.                          * top of the heap into it, if we're done with the
  1619.                          * last promotion, store the "least competent"
  1620.                          * worker, and exit.
  1621.                          */
  1622.                         twistDownHdl = heapVector[lastIndex];
  1623.                         heapVector[lastIndex] = heapVector[1];
  1624.                         if (--lastIndex <= 1) {
  1625.                             heapVector[1] = twistDownHdl;
  1626.                             break;            /* Exit the for (;;) loop        */
  1627.                         }
  1628.                     }
  1629.                     /*
  1630.                      * Whether we are in the hiring or promotion phase, we
  1631.                      * continue here to sift the current element into its
  1632.                      * proper position.
  1633.                      */
  1634.                     i = siftIndex;
  1635.                     j = siftIndex << 1;
  1636.                     while (j <= lastIndex) {
  1637.                         if (j < lastIndex
  1638.                          && (*sortCompareProc)(
  1639.                                     refCon,
  1640.                                     heapVector[j],
  1641.                                     heapVector[j + 1]
  1642.                                 ) < 0) {
  1643.                             ++j;
  1644.                         }
  1645.                         if ((*sortCompareProc)(
  1646.                                     refCon, twistDownHdl, heapVector[j]
  1647.                                 ) < 0) {
  1648.                             heapVector[i] = heapVector[j];
  1649.                             i = j;
  1650.                             j += i;
  1651.                         }
  1652.                         else {
  1653.                             break;
  1654.                         }
  1655.                     }
  1656.                     heapVector[i] = twistDownHdl;
  1657.                 }
  1658.                 /*
  1659.                  * heapVector has the sorted list. Build the result list.
  1660.                  */
  1661.                 *twistDownHdlPtr = heapVector[1];
  1662.                 for (i = 2; i <= nElements; i++)
  1663.                     (**(heapVector[i - 1])).nextElement = heapVector[i];
  1664.                 (**(heapVector[nElements])).nextElement = NULL;
  1665.                 DisposePtr((Ptr) heapVector);
  1666.             }
  1667.         }
  1668.         return (status);
  1669.  
  1670. }
  1671.  
  1672.  
  1673. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1674.  * AdjustHorizontalScrollbar
  1675.  *
  1676.  * This is called when the user clicks in the horizontal scrollbar that was
  1677.  * created when the list was created. Note that we handle the scrollbar here,
  1678.  * as it is not stored within the ListManager list record.
  1679.  */
  1680. static void
  1681. AdjustHorizontalScrollbar(
  1682.         ListHandle                theList
  1683.     )
  1684. {
  1685.         short                    horizontalMax;
  1686.         ControlHandle            theControl;
  1687.         
  1688.         theControl = PRIVATE.hScroll;
  1689.         horizontalMax = kMaxHorizontalScroll - width((**theControl).contrlRect);
  1690.         if (horizontalMax < 0)
  1691.             horizontalMax = 0;
  1692.         SetControlMinimum(theControl, 0);
  1693.         SetControlMaximum(theControl, horizontalMax);
  1694.         HiliteControl(
  1695.             theControl,
  1696.             (horizontalMax == 0) ? kDisabledControl : kActiveControl
  1697.         );
  1698. }
  1699.  
  1700. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1701.  * ScrollTwistDownActionProc
  1702.  *
  1703.  * This is called by TrackControl when the user clicks in the horizontal
  1704.  * scrollbar that was created when the list was created. Note that we handle
  1705.  * the scrollbar here, as it is not stored within the ListManager list record.
  1706.  */
  1707. static pascal void
  1708. ScrollTwistDownActionProc(
  1709.         register ControlHandle    theControl,
  1710.         short                    partCode
  1711.     )
  1712. {
  1713.         short                    delta;
  1714.         
  1715.         delta = (width((**theControl).contrlRect) * 7) / 8;
  1716.         switch (partCode) {
  1717.         // use new names  dbt 4/24/96
  1718.         case kControlUpButtonPart:        delta = -CharWidth('M');        break;
  1719.         case kControlPageUpPart:        delta = (-delta);                break;
  1720.         case kControlDownButtonPart:    delta = CharWidth('M');            break;
  1721.         case kControlPageDownPart:        /* All set */                    break;
  1722.         default:                        return;            /* Mouse exited control    */
  1723.         }
  1724.         SetControlValue(theControl, GetControlValue(theControl) + delta);
  1725.         ScrollTwistDownList(theControl);
  1726. }
  1727.  
  1728. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1729.  * ScrollTwistDownList
  1730.  *
  1731.  * This is called when the user clicks in the horizontal scrollbar that was
  1732.  * created when the list was created. This is the function that modifies the
  1733.  * scrollbar.
  1734.  */
  1735. static void
  1736. ScrollTwistDownList(
  1737.         register ControlHandle    theControl
  1738.     )
  1739. {
  1740.         ListHandle                theList;
  1741.         short                    delta;
  1742.         RgnHandle                clipRgn;
  1743.         RgnHandle                updateRgn;
  1744.         Rect                    viewRect;
  1745.         
  1746.         theList = (ListHandle) GetControlReference(theControl);
  1747.         /*
  1748.          * LIST.indent.h is negative when the cell is scrolled left. Get its
  1749.          * current amount (as a positive value) and set delta to the amount
  1750.          * that must be scrolled. Delta will be positive to scroll right
  1751.          * (meaning that the scroll bar moved left). This probably won't
  1752.          * work for a right-to-left language.
  1753.          */
  1754.         delta = kZeroIndent - LIST.indent.h - GetControlValue(theControl);
  1755.         if (delta != 0) {
  1756.             /*
  1757.              * We must scroll the list. Get a clip rectangle so the scrolling
  1758.              * is limited to the drawing area, scroll it, and update anything
  1759.              * that came into view. Hmm, should the buttons ever scroll?
  1760.              */
  1761.             viewRect = LIST.rView;
  1762.             clipRgn = NewRgn();
  1763.             updateRgn = NewRgn();
  1764.             GetClip(clipRgn);
  1765.             ClipRect(&viewRect);
  1766.             ScrollRect(&viewRect, delta, 0, updateRgn);
  1767.             LIST.indent.h += delta;
  1768.             LUpdate(updateRgn, theList);
  1769.             SetClip(clipRgn);
  1770.             DisposeRgn(updateRgn);
  1771.             DisposeRgn(clipRgn);
  1772.         }
  1773. }
  1774.  
  1775. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1776.  * Print Manager
  1777.  *
  1778.  * The following section lets the user print a twistdown list.
  1779.  */
  1780.  
  1781. static pascal OSErr            DefaultTwistDownPrintSetupProc(
  1782.         ListHandle                theList,
  1783.         THPrint                    hPrint,
  1784.         void                    *clientData,
  1785.         StringPtr                dateString
  1786.     );
  1787. static pascal OSErr            DefaultTwistDownPrintImageProc(
  1788.         ListHandle                theList,
  1789.         THPrint                    hPrint,
  1790.         void                    *clientData,
  1791.         StringPtr                dateString,
  1792.         const Rect                *pageRect,
  1793.         short                    pageNumber
  1794.     );
  1795.  
  1796. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1797.  * PrintTwistDownList
  1798.  *
  1799.  * Mainline print handler. This calls task-specific routines to count the number
  1800.  * of pages and to image each page. The caller may provide special routines for
  1801.  * these tasks, or use the default calls (that presume text-only list elements).
  1802.  * This is modified from Rich Siegel's generic print driver.
  1803.  */
  1804. pascal OSErr
  1805. PrintTwistDownList(
  1806.         ListHandle                theList,
  1807.         THPrint                    *printHandlePtr,
  1808.         Boolean                    doStyleDialog,
  1809.         TwistDownPrintSetupProc    twistDownPrintSetupProc,
  1810.         TwistDownPrintImageProc    twistDownPrintImageProc,
  1811.         TwistDownPrintExitProc    twistDownPrintExitProc,
  1812.         void                    *clientData
  1813.     )
  1814. {
  1815.         OSErr                    status;            /* Current error                */
  1816.         THPrint                    hPrint;            /* Copy of *printHandlePtr        */
  1817.         Boolean                    printIsOpen;    /* PROpen ... PRClose            */
  1818.         Boolean                    docIsOpen;        /* PROpenDoc ... PRCloseDoc        */
  1819.         short                    nCopies;        /* Number of copies to print    */
  1820.         short                    iCopy;            /* Which copy are we printing?    */
  1821.         short                    pageNumber;        /* Which page are we printing?    */
  1822.         Rect                    pageRect;        /* Current page image rectangle    */
  1823.         short                    printDevice;    /* What kind of printer?        */
  1824.         Boolean                    draftMode;        /* Draft or spool?                */
  1825.         TPPrPort                printPort;        /* The print port                */
  1826.         TPrStatus                printStatus;    /* PrPicFile status info        */
  1827.         GrafPtr                    savePort;        /* Old GrafPort                    */
  1828.         unsigned long            now;            /* When we're called            */
  1829.         Str255                    dateString;        /* Handy date + time string        */
  1830. #ifndef bDevCItoh
  1831. #define bDevCItoh            1
  1832. #endif
  1833. #ifndef bDevLaser
  1834. #define bDevLaser            3
  1835. #endif
  1836.         enum {
  1837.             kImageWriter        = bDevCItoh,
  1838.             kLaserWriter        = bDevLaser
  1839.         };
  1840. /*
  1841.  * This macro exits the print handler on any error.
  1842.  */
  1843. #define CheckError(stat) do {                    \
  1844.         if ((status = (stat)) != noErr)            \
  1845.             goto exit;                            \
  1846.     } while (0)
  1847. #if 1 /* Debug only */
  1848. #define CHECK(stat, why) do {                                    \
  1849.         OSErr                checkStat;                            \
  1850.         extern short NonFatalError(OSErr, ConstStr255Param);    \
  1851.         if ((checkStat = (stat)) != noErr)                        \
  1852.             NonFatalError(checkStat, why);                        \
  1853.     } while (0)
  1854. #else
  1855. #define CHECK(stat, why) /* Nothing */
  1856. #endif
  1857.  
  1858.         GetPort(&savePort);
  1859.         status = noErr;
  1860.         printIsOpen = FALSE;
  1861.         docIsOpen = FALSE;
  1862.         if (twistDownPrintSetupProc == NULL)
  1863.             twistDownPrintSetupProc = DefaultTwistDownPrintSetupProc;
  1864.         if (twistDownPrintImageProc == NULL)
  1865.             twistDownPrintImageProc = DefaultTwistDownPrintImageProc;
  1866.         /*
  1867.          * Set the date and time
  1868.          */
  1869.         GetDateTime(&now);
  1870.         IUDateString(now, abbrevDate, dateString);
  1871.         dateString[++dateString[0]] = ' ';
  1872.         dateString[++dateString[0]] = '/';
  1873.         iCopy = ++dateString[0];
  1874.         IUTimeString(now, FALSE, &dateString[iCopy]);
  1875.         dateString[0] += (dateString[iCopy] + 1);
  1876.         dateString[iCopy] = ' ';
  1877.         /*
  1878.          * If there is no Print Handle, allocate one and fail on errors. On exit,
  1879.          * the print handler is retained so that multiple printouts retain the
  1880.          * same user selections.
  1881.          */
  1882.         PrOpen();
  1883.         status = PrError();
  1884.         CHECK(status, "\pCan't open print manager");
  1885.         CheckError(status);
  1886.         printIsOpen = TRUE;
  1887.         if (*printHandlePtr == NULL) {
  1888.             *printHandlePtr = (THPrint) NewHandle(sizeof (TPrint));
  1889.             if (*printHandlePtr == NULL) {
  1890.                 status = MemError();
  1891.                 CHECK(status, "\pNewHandle(sizeof (TPrint)) failed");
  1892.                 goto exit;
  1893.             }
  1894.             PrintDefault(*printHandlePtr);
  1895.         }
  1896.         hPrint = (*printHandlePtr);
  1897.         /*
  1898.          * Validate the Print Handle and call the Print Style dialog if necessary.
  1899.          * If the user cancels, exit (noErr). Then call the job dialog to get the
  1900.          * number of copies (exit on cancel here, too). Note that we don't exit
  1901.          * with userCanceledErr, as this is merely informative.
  1902.          */
  1903.         if (PrValidate(hPrint) || doStyleDialog) {
  1904.             if (PrStlDialog(hPrint) == FALSE) {
  1905.                 CHECK(userCanceledErr, "\pUser canceled style dialog");
  1906.                 goto exit;
  1907.             }
  1908.         }
  1909.         if (PrJobDialog(hPrint) == FALSE) {
  1910.             CHECK(userCanceledErr, "\pUser canceled job dialog");
  1911.             goto exit;
  1912.         }
  1913.         /*
  1914.          * Setup is done, call the user's setup procedure and exit on errors.
  1915.          * The setup function must set the number of pages in the document and
  1916.          * return noErr to continue.
  1917.          */
  1918.         SetCursor(*GetCursor(watchCursor));
  1919.         status = (*twistDownPrintSetupProc)
  1920.                     (theList, hPrint, clientData, dateString);
  1921.         CHECK(status, "\pUser setup function failed");
  1922.         if (status != noErr)
  1923.             goto exit;
  1924.         /*
  1925.          * Grab some information for the loops that follow.
  1926.          * printDevice is the printing device. This is only interesting for
  1927.          * ImageWriter (StyleWriter?) compatibility, and hasn't been tested
  1928.          * in many years.
  1929.          */
  1930.         printDevice = ((**hPrint).prStl.wDev >> 8) & 0xFF;
  1931.         draftMode = (**hPrint).prJob.bJDocLoop == bDraftLoop;
  1932.         if (draftMode && printDevice == kImageWriter)
  1933.             nCopies = (**hPrint).prJob.iCopies;
  1934.         else {
  1935.             nCopies = 1;
  1936.         }
  1937.         /*
  1938.          * Printing begins here.
  1939.          */
  1940.         printPort = PrOpenDoc(hPrint, NULL, NULL);
  1941.         docIsOpen = TRUE;
  1942.         status = PrError();
  1943.         CHECK(status, "\pCan't open document to print");
  1944.         CheckError(status);
  1945.         /*
  1946.          * Note: if you select "Print to EPS file", you can only image
  1947.          * one page, the first in this case.
  1948.          */
  1949.         for (iCopy = 1; iCopy <= nCopies; iCopy++) {
  1950.             for (pageNumber = (**hPrint).prJob.iFstPage;
  1951.                     pageNumber <= (**hPrint).prJob.iLstPage;
  1952.                     pageNumber++) {
  1953.                 SetCursor(*GetCursor(watchCursor));
  1954.                 PrOpenPage(printPort, NULL);
  1955.                 status = PrError();
  1956.                 if (status == noErr) {
  1957.                     pageRect = (**hPrint).prInfo.rPage;
  1958.                     status = (*twistDownPrintImageProc)(
  1959.                                 theList,
  1960.                                 hPrint,
  1961.                                 clientData,
  1962.                                 dateString,
  1963.                                 &pageRect,
  1964.                                 pageNumber
  1965.                             );
  1966.                 }
  1967.                 PrClosePage(printPort);
  1968.                 if (status == noErr)
  1969.                     status = PrError();
  1970.                 CHECK(status, "\pPrint page error");
  1971.                 CheckError(status);
  1972.             }
  1973.         }
  1974.         /*
  1975.          * Normal exit
  1976.          */
  1977.         SetPort(savePort);
  1978.         PrCloseDoc(printPort);
  1979.         docIsOpen = FALSE;
  1980.         status = PrError();
  1981.         CHECK(status, "\pCan't close document after printing complete");
  1982.         CheckError(status);
  1983.         if (draftMode == FALSE) {
  1984.             PrPicFile(hPrint, NULL, NULL, NULL, &printStatus);
  1985.             status = PrError();
  1986.             CHECK(status, "\pCan't spool document after printing complete");
  1987.         }
  1988.         /*
  1989.          * Everyone exits here.
  1990.          */
  1991. exit:    SetPort(savePort);
  1992.         if (docIsOpen)
  1993.             PrCloseDoc(printPort);
  1994.         if (printIsOpen)
  1995.             PrClose();
  1996.         if (twistDownPrintExitProc != NULL)
  1997.             status = (*twistDownPrintExitProc)(theList, clientData, status);
  1998.         InitCursor();
  1999.         return (status);
  2000. }
  2001.  
  2002. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2003.  * DefaultTwistDownPrintSetupProc
  2004.  *
  2005.  * This will be called if the user didn't provide a print setup procedure. Our
  2006.  * default output page will have the date on the top-left and the page number on
  2007.  * the top right (both in 9 pt Helvetica Bold). This function must set the actual
  2008.  * number of pages that are to be printed.
  2009.  *
  2010.  * Note: the print functions have not been upgraded for right-to-left languages.
  2011.  */
  2012. static pascal OSErr
  2013. DefaultTwistDownPrintSetupProc(
  2014.         ListHandle                theList,
  2015.         THPrint                    hPrint,
  2016.         void                    *clientData,
  2017.         StringPtr                dateString
  2018.     )
  2019. {
  2020.         unsigned short            lineHeight;
  2021.         unsigned short            linesInPrintout;
  2022.         unsigned short            linesPerPage;
  2023.         unsigned short            nPages;
  2024.         unsigned short            headerHeight;
  2025.         Rect                    printRect;
  2026.         FontInfo                info;
  2027.         short                    fontNumber;
  2028.         
  2029.         UNUSED(clientData);
  2030.         UNUSED(dateString);
  2031.         SetPort(LIST.port);
  2032.         GetFNum(kPrintoutHeaderFont, &fontNumber);
  2033.         TextFont(fontNumber);
  2034.         TextSize(kPrintoutHeaderFontSize);
  2035.         GetFontInfo(&info);
  2036.         headerHeight = info.ascent + info.descent + info.leading;
  2037.         /*
  2038.          * Get the display page rectangle, remove the header, and determine
  2039.          * the number of lines that will fit on one page and, from that,
  2040.          * the number of pages in the printout. If this is less than the
  2041.          * default (999), adjust the print record.
  2042.          */
  2043.         printRect = (**hPrint).prInfo.rPage;
  2044.         printRect.top += (headerHeight + kPrintoutHeaderGap);
  2045.         linesInPrintout = height(LIST.dataBounds);
  2046.         TextFont(PRIVATE.fontNumber);
  2047.         TextSize(PRIVATE.fontSize);
  2048.         TextFace(normal);
  2049.         GetFontInfo(&info);
  2050.         lineHeight = info.ascent + info.descent + info.leading;
  2051.         linesPerPage = height(printRect) / lineHeight;
  2052.         nPages = (linesInPrintout + linesPerPage - 1) / linesPerPage;
  2053.         /*
  2054.          * We want to print nPages. Set this into the print record if the
  2055.          * caller asked for more (i.e. for the default "all pages").
  2056.          */
  2057.         if ((**hPrint).prJob.iLstPage > nPages)
  2058.             (**hPrint).prJob.iLstPage = nPages;
  2059.         return (noErr);
  2060. }
  2061.  
  2062. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2063.  * DefaultTwistDownPrintImageProc
  2064.  *
  2065.  * Image the current page. This must duplicate some of the code from the
  2066.  * print setup function (above). This needs work for right-to-left languages.
  2067.  *
  2068.  * The page is imaged using the currently selected display font and font size.
  2069.  */
  2070. static pascal OSErr
  2071. DefaultTwistDownPrintImageProc(
  2072.         ListHandle                theList,
  2073.         THPrint                    hPrint,
  2074.         void                    *clientData,
  2075.         StringPtr                dateString,
  2076.         const Rect                *pageRect,
  2077.         short                    pageNumber
  2078.     )
  2079. {
  2080.         unsigned short            i;
  2081.         unsigned short            lastRow;
  2082.         unsigned short            linesPerPage;
  2083.         unsigned short            headerHeight;
  2084.         Rect                    printRect;
  2085.         short                    lineHeight;
  2086.         Cell                    listCell;
  2087.         FontInfo                info;
  2088.         short                    fontNumber;
  2089.         Str255                    work;
  2090.         short                    cellSize;
  2091.         TwistDownHdl            twistDownHandle;
  2092.         register TwistDownPtr    twistDownPtr;
  2093.         signed char                elementLockState;
  2094.         
  2095.         UNUSED(clientData);
  2096.         UNUSED(hPrint);
  2097.         /*
  2098.          * First, image the header.
  2099.          */
  2100.         GetFNum(kPrintoutHeaderFont, &fontNumber);
  2101.         TextFont(fontNumber);
  2102.         TextSize(kPrintoutHeaderFontSize);
  2103.         TextFace(kPrintoutHeaderStyle);
  2104.         GetFontInfo(&info);
  2105.         headerHeight = info.ascent + info.descent + info.leading;
  2106.         MoveTo(pageRect->left, pageRect->top + info.ascent);
  2107.         DrawString(dateString);
  2108.         pstrcpy(work, "\pPage: ");
  2109.         i = work[0];
  2110.         NumToString(pageNumber, &work[i]);
  2111.         work[0] += work[i];
  2112.         work[i] = ' ';
  2113.         MoveTo(pageRect->right - StringWidth(work), pageRect->top + info.ascent);
  2114.         DrawString(work);
  2115.         printRect = *pageRect;
  2116.         printRect.top += (headerHeight + kPrintoutHeaderGap);
  2117.         /*
  2118.          * Now, do the list data itself.
  2119.          */
  2120.         TextFont(PRIVATE.fontNumber);
  2121.         TextSize(PRIVATE.fontSize);
  2122.         TextFace(normal);
  2123.         GetFontInfo(&info);
  2124.         lineHeight = info.ascent + info.descent + info.leading;
  2125.         linesPerPage = height(printRect) / lineHeight;
  2126.         listCell.h = 0;
  2127.         listCell.v = (pageNumber - 1) * linesPerPage + LIST.dataBounds.top;
  2128.         lastRow = listCell.v + linesPerPage;
  2129.         if (lastRow > LIST.dataBounds.bottom)
  2130.             lastRow = LIST.dataBounds.bottom;
  2131.         printRect.bottom = printRect.top + lineHeight;
  2132.         while (listCell.v < lastRow) {
  2133.             cellSize = sizeof twistDownHandle;
  2134.             LGetCell(&twistDownHandle,    &cellSize, listCell, theList);
  2135.             if (cellSize == sizeof twistDownHandle && twistDownHandle != NULL) {
  2136.                 elementLockState = HGetState((Handle) twistDownHandle);
  2137.                 HLock((Handle) twistDownHandle);
  2138.                 twistDownPtr = (*twistDownHandle);
  2139.                 (PRIVATE.drawProc)(theList, twistDownPtr, &printRect);
  2140.                 HSetState((Handle) twistDownHandle, elementLockState);
  2141.             }
  2142.             OffsetRect(&printRect, 0, lineHeight);
  2143.             ++listCell.v;
  2144.         }
  2145.         return (noErr);
  2146. }
  2147.  
  2148. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2149.  * DefaultTwistDownDrawProc
  2150.  *
  2151.  * Draw the current list element in the viewRect. The caller has setup the font
  2152.  * information.
  2153.  */
  2154. static pascal void
  2155. DefaultTwistDownDrawProc(
  2156.         ListHandle                theList,            /* The list itself            */
  2157.         TwistDownPtr            twistDownPtr,        /* Locked data handle        */
  2158.         const Rect                *viewRect            /* Draw in this area        */
  2159.     )
  2160. {
  2161.         short                    textWidth;
  2162. #define ELEM (*twistDownPtr)
  2163.  
  2164.         if (PRIVATE.isLeftJustify)
  2165.             MoveTo(viewRect->left, viewRect->top + LIST.indent.v);
  2166.         else {
  2167.             textWidth = TextWidth(ELEM.data, 0, ELEM.dataLength);
  2168.             MoveTo(viewRect->right - textWidth, viewRect->top + LIST.indent.v);
  2169.         }
  2170.         DrawText(ELEM.data, 0, ELEM.dataLength);
  2171. #undef ELEM
  2172. }
  2173.  
  2174. static void
  2175. pstrcpy(
  2176.         StringPtr                dst,
  2177.         ConstStr255Param        src
  2178.     )
  2179. {
  2180.         BlockMoveData(src, dst, src[0] + 1);
  2181. }
  2182.  
  2183. static void
  2184. pstrcat(
  2185.         StringPtr                dst,
  2186.         ConstStr255Param        src
  2187.     )
  2188. {
  2189.         short                    length;
  2190.         
  2191.         length = 255 - dst[0];
  2192.         if (length > src[0])
  2193.             length = src[0];
  2194.         BlockMoveData(&src[1], &dst[1] + dst[0], length);
  2195.         dst[0] += length;
  2196. }
  2197.